/***********************************************************************
 * Module:  MoveCommand.java
 * Author:  Nikola
 * Purpose: Defines the Class MoveCommand
 ***********************************************************************/

package genedit.controller;

import genedit.model.graph.BreakPoint;
import genedit.model.graph.ConnectionPoint;
import genedit.model.graph.GraphElement;
import genedit.model.graph.Link;

import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Stack;

public class MoveCommand extends Command {
	private double moveByX = 0;
	private double moveByY = 0;
	private double globalDiffX = 0;
	private double globalDiffY = 0;
	//sluzi da se evidentira undo - nakon undo nece se koristiti moveBy nego globalDiff
	private boolean useGlobalDiff;

	private Set<GraphElement> movedElements;
	private HashMap<Link,ArrayList<BreakPoint>> breakpointsMemento;
	//služi da se zapamćene tačke prenesu od prvog move-a do onoga koji će se zapamtiti i kasnije koristiti u undo
	public static HashMap<Link,ArrayList<BreakPoint>> breakpointsMementoTranslator=null;
	/** @param isUndoable */
	public MoveCommand(boolean isUndoable, double diffX, double diffY) {
		super(isUndoable);
		globalDiffX = diffX;
		globalDiffY = diffY;
		breakpointsMemento=null;
		useGlobalDiff=false;
	}

	public void execute() {
		//ako je ranije bilo izvršavanja ove komande korisit globalnu razliku koordinata
		if(useGlobalDiff){
			moveByX=globalDiffX;
			moveByY=globalDiffY;
		}
		
		Iterator<GraphElement> iterator = getIteratorMovedElements();
		//ako se tek ulazi u move zapamti sve BreakPoints-e
		if(!useGlobalDiff && breakpointsMementoTranslator==null){
			breakpointsMementoTranslator=new HashMap<Link,ArrayList<BreakPoint>>();
			ArrayList<BreakPoint> breakpointsCopy;
			while (iterator.hasNext()) {
				GraphElement current = iterator.next();
				if (!(current instanceof Link)) {
					for (ConnectionPoint cp : current.getConnectionPoints()) {				
						//backup breakpoints
						for(Link link:cp.getStarting()){
							if(!breakpointsMementoTranslator.containsKey(link)){
								breakpointsCopy=new ArrayList<BreakPoint>();
								for(BreakPoint bp : link.getBreakPoints()){
									breakpointsCopy.add(new BreakPoint((Point2D)bp.getPosition().clone()));
								}
								breakpointsMementoTranslator.put(link, breakpointsCopy);
							}
						}
						for(Link link:cp.getEnding()){
							if(!breakpointsMementoTranslator.containsKey(link)){
								breakpointsCopy=new ArrayList<BreakPoint>();
								for(BreakPoint bp : link.getBreakPoints()){
									breakpointsCopy.add(new BreakPoint((Point2D)bp.getPosition().clone()));
								}
								breakpointsMementoTranslator.put(link, breakpointsCopy);
							}
						}
					}
				}
			}
		}
		
		iterator = getIteratorMovedElements();
		//pomeri samo elemente
		while (iterator.hasNext()) {
			GraphElement current = iterator.next();
			if (!(current instanceof Link)) {
				Point2D elementPosition = current.getPosition();
				elementPosition.setLocation(elementPosition.getX() + moveByX,
						elementPosition.getY() + moveByY);
				getDocument().getPainter(current).refreshAffine();
			}
		}
		
		iterator = getIteratorMovedElements();
		//pomeri veze i konekcione tacke
		while (iterator.hasNext()) {
			GraphElement current = iterator.next();
			if (current instanceof Link) { // pomeramo sve breakpointove
											// ukoliko je veza
				Link link = (Link) current;
				if (link.getStart() != null && link.getEnd() != null
						&& movedElements.contains(link.getStart().getParent())
						&& movedElements.contains(link.getEnd().getParent())) {
					for (BreakPoint bp : link.getBreakPoints())
						bp.getPosition().setLocation(bp.getPosition().getX() + moveByX,
								bp.getPosition().getY() + moveByY);

				}	
			} else { // za obicni element poziciju i sve njegove connection
						// pointove
				for (ConnectionPoint cp : current.getConnectionPoints()) {				
					cp.getPosition().setLocation(cp.getPosition().getX() + moveByX,
							cp.getPosition().getY() + moveByY);
				}
			}
		}
		
		iterator=getIteratorMovedElements();
		//popravi preseke sa elementima
		while (iterator.hasNext()) {
			GraphElement current = iterator.next();
			if (!(current instanceof Link)) {
				for (ConnectionPoint cp : current.getConnectionPoints()) {
					for (Link link1 : cp.getEnding()) {
						if (!movedElements.contains(getDocument().getPainter(link1))) {
							link1.removeIntersectPoints();
							link1.addIntersectionPoints();
						}
					}
					for (Link link1 : cp.getStarting()) {
						if (!movedElements.contains(getDocument().getPainter(link1))) {
							link1.removeIntersectPoints();
							link1.addIntersectionPoints();
						}
					}
				}
			}
		}
		
		// ako je kraj izrade veze
		if (undoable) {
			//omogući izvršavanje budućih move komandi
			if(!useGlobalDiff){
				breakpointsMemento=breakpointsMementoTranslator;
				breakpointsMementoTranslator=null;
			}
			
			//ispravi linkove
			for (GraphElement element : movedElements) {
				if (element instanceof Link) {
					Link link = (Link) element;
					link.removeIntersectPoints();
					link.rectLink();
					link.addIntersectionPoints();
				} else{
					for (ConnectionPoint point : element.getConnectionPoints()) {
						for (Link link : point.getStarting()) {
							link.removeIntersectPoints();
							link.rectLink();
							link.addIntersectionPoints();
						}
						for (Link link : point.getEnding()) {
							link.removeIntersectPoints();
							link.rectLink();
							link.addIntersectionPoints();
						}
					}
				}
			}
		}
		
		getDocument().getModel().refresh();
	}

	public void undo() {
		//označi da je došlo do undo
		useGlobalDiff=true;
		
		Iterator<GraphElement> iterator = getIteratorMovedElements();
		//vrati samo elemente
		while (iterator.hasNext()) {
			GraphElement current = iterator.next();
			if (!(current instanceof Link)) {
				Point2D elementPosition = current.getPosition();
				elementPosition.setLocation(elementPosition.getX() - globalDiffX,
						elementPosition.getY() - globalDiffY);
				getDocument().getPainter(current).refreshAffine();
			}
		}
		
		iterator = getIteratorMovedElements();
		//vrati veze i konekcione tacke
		while (iterator.hasNext()) {
			GraphElement current = iterator.next();
			if (current instanceof Link) { // pomeramo sve breakpointove
											// ukoliko je veza
				Link link = (Link) current;
				if (link.getStart() != null && link.getEnd() != null
						&& movedElements.contains(link.getStart().getParent())
						&& movedElements.contains(link.getEnd().getParent())) {
					for (BreakPoint bp : link.getBreakPoints())
						bp.getPosition().setLocation(bp.getPosition().getX() - globalDiffX,
								bp.getPosition().getY() - globalDiffY);

				}	
			} else { // za obicni element poziciju i sve njegove connection
						// pointove
				for (ConnectionPoint cp : current.getConnectionPoints()) {
					cp.getPosition().setLocation(cp.getPosition().getX() - globalDiffX,
							cp.getPosition().getY() - globalDiffY);
				}
			}
		}
		
		iterator=getIteratorMovedElements();
		//vrati zapamćene breakpointe
		while (iterator.hasNext()) {
			GraphElement current = iterator.next();
			if (!(current instanceof Link)) {
				for (ConnectionPoint cp : current.getConnectionPoints()) {
					for (Link link1 : cp.getEnding()) {
						ArrayList<BreakPoint> points=breakpointsMemento.get(link1);
						//ako ima zapamćenih breakpointa
						if (points!=null) {
							link1.setBreakPoints(points);
						}
					}
					for (Link link1 : cp.getStarting()) {
						ArrayList<BreakPoint> points=breakpointsMemento.get(link1);
						//ako ima zapamćenih breakpointa
						if (points!=null) {
							link1.setBreakPoints(points);
						}
					}
				}
			}
		}
		
		getDocument().getModel().refresh();
	}

	public double getMoveByX() {
		return moveByX;
	}

	/** @param newMoveByX */
	public void setMoveByX(double newMoveByX) {
		moveByX = newMoveByX;
	}

	public double getMoveByY() {
		return moveByY;
	}

	/** @param newMoveByY */
	public void setMoveByY(double newMoveByY) {
		moveByY = newMoveByY;
	}

	/** @pdGenerated default getter */
	public Set<GraphElement> getMovedElements() {
		if (movedElements == null)
			movedElements = new java.util.HashSet<GraphElement>();
		return movedElements;
	}

	/** @pdGenerated default iterator getter */
	public java.util.Iterator<GraphElement> getIteratorMovedElements() {
		if (movedElements == null)
			movedElements = new java.util.HashSet<GraphElement>();
		return movedElements.iterator();
	}

	/**
	 * @pdGenerated default setter
	 * @param newMovedElements
	 */
	public void setMovedElements(java.util.Collection<GraphElement> newMovedElements) {
		removeAllMovedElements();
		for (java.util.Iterator<GraphElement> iter = newMovedElements.iterator(); iter.hasNext();)
			addMovedElements((GraphElement) iter.next());
	}

	/**
	 * @pdGenerated default add
	 * @param newElementPainter
	 */
	public void addMovedElements(GraphElement newElementPainter) {
		if (newElementPainter == null)
			return;
		if (this.movedElements == null)
			this.movedElements = new java.util.HashSet<GraphElement>();
		if (!this.movedElements.contains(newElementPainter))
			this.movedElements.add(newElementPainter);
	}

	/**
	 * @pdGenerated default remove
	 * @param oldElementPainter
	 */
	public void removeMovedElements(GraphElement oldElementPainter) {
		if (oldElementPainter == null)
			return;
		if (this.movedElements != null)
			if (this.movedElements.contains(oldElementPainter))
				this.movedElements.remove(oldElementPainter);
	}

	/** @pdGenerated default removeAll */
	public void removeAllMovedElements() {
		if (movedElements != null)
			movedElements.clear();
	}

}